package it.unibo.refolding.pico.ui;

import it.unibo.refolding.common.CommonThings;
import it.unibo.refolding.common.IntPair;
import it.unibo.refolding.pico.PeakSlopes;
import it.unibo.refolding.pico.PicoShapes;
import it.unimi.dsi.fastutil.shorts.ShortList;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

public class PeakSlopesDrawer {
  int lowestPressedIndex = -1;
  int highestDraggedIndex = -1;
  int segment;

  final double defScale;

  private final short minZ;
  private final short minDef;
  private final List<List<Map<Color, List<Shape>>>> peakShapes;
  private final PeakSlopesHolder h;
  private final Color currentPeakColor;
  private final List<Color> segmentPeakColors;

  public PeakSlopesDrawer(double defScale, PeakSlopesHolder h, Color currentPeakColor,
    List<Color> segmentPeakColors) {
    minZ = CommonThings.min(h.sharedZ());
    minDef = CommonThings.min(h.sharedDef());
    this.defScale = defScale;
    this.h = h;
    peakShapes = new ArrayList<>();
    for (int i = 0; i < h.loopCount(); ++i) {
      PeakSlopes ps = h.peakSlopes(i);
      List<Map<Color, List<Shape>>> l = new ArrayList<>();
      for (IntPair ip : ps) {
        l.add(peakToShapes(
          h.sharedZ(), h.sharedDef(), defScale, h.retractFrom(i) + h.offset(i), minZ, minDef, ip,
          segmentPeakColors.get(i)));
      }
      peakShapes.add(l);
    }
    Preconditions.checkArgument(segmentPeakColors.size() == h.loopCount());
    this.currentPeakColor = currentPeakColor;
    this.segmentPeakColors = segmentPeakColors;
  }

  private static Map<Color, List<Shape>> peakToShapes(
    ShortList z, ShortList def, double defScale, int contactPointFullOffset, short minZ, short minDef,
    IntPair ip, Color color) {
    final int halfSquareSide = 10;
    final int side = 2 * halfSquareSide + 1;
    Path2D.Float generalPath = PicoShapes.createPath(
      z, def, defScale, ip.x + contactPointFullOffset, ip.y + contactPointFullOffset + 1, minZ,
      minDef);
    int xStart = z.getShort(contactPointFullOffset + ip.y) - halfSquareSide;
    float yStart = (float) ((def.getShort(contactPointFullOffset + ip.y) - minDef) * defScale)
      - halfSquareSide;
    Rectangle2D peakRectangle = new Rectangle2D.Float(xStart - minZ, yStart, side, side);
    Line2D.Float peakLine1 = new Line2D.Float(xStart - minZ, yStart, xStart - minZ + side, yStart
      + side);
    Line2D.Float peakLine2 = new Line2D.Float(xStart - minZ + side, yStart, xStart - minZ, yStart
      + side);
    xStart = z.getShort(contactPointFullOffset + ip.x) - halfSquareSide;
    yStart = (float) ((def.getShort(contactPointFullOffset + ip.x) - minDef) * defScale);
    Ellipse2D.Float peakStartEllipse = new Ellipse2D.Float(xStart - minZ, yStart, side, side);
    double factor = 0.75;
    Color annotationColor = new Color(
      (int) (color.getRed() * factor), (int) (color.getGreen() * factor),
      (int) (color.getBlue() * factor), color.getAlpha());
    return CommonThings.unionMapsOfLists(ImmutableMap.<Color, List<Shape>>of(
      color, ImmutableList.<Shape>of(generalPath)),
      ImmutableMap.<Color, List<Shape>>of(
        annotationColor, ImmutableList.<Shape>of(peakLine1, peakLine2, peakRectangle, peakStartEllipse)));
  }

  public void paintComponent(Graphics2D g2) {
    List<Map<Color, List<Shape>>> segmentL = null;
    for (ListIterator<List<Map<Color, List<Shape>>>> it = peakShapes.listIterator(); it.hasNext(); ) {
      List<Map<Color, List<Shape>>> l = it.next();
      if (it.previousIndex() == segment) {
        segmentL = l;
      } else {
        paint(l, g2);
      }
    }
    // Draw the peaks of the current segment last.
    paint(segmentL, g2);
    assert highestDraggedIndex == -1 | lowestPressedIndex <= highestDraggedIndex;
    if (highestDraggedIndex != -1) {
      assert lowestPressedIndex != -1;
      Path2D.Float path = PicoShapes.createPath(
        h.sharedZ(), h.sharedDef(), defScale,
        lowestPressedIndex + h.retractFrom(segment) + h.offset(segment),
        highestDraggedIndex + h.retractFrom(segment) + h.offset(segment) + 1, minZ, minDef);
      g2.setColor(currentPeakColor);
      g2.draw(path);
    }
  }

  public void paint(List<Map<Color, List<Shape>>> l, Graphics2D g2) {
    for (Map<Color, List<Shape>> m : l) {
      for (Entry<Color, List<Shape>> e : m.entrySet()) {
        g2.setColor(e.getKey());
        for (Shape s : e.getValue()) {
          g2.draw(s);
        }
      }
    }
  }

  public void removedPeakSlope(int indexRemoved) {
    peakShapes.get(segment).remove(indexRemoved);
  }

  public void addedPeakSlope(int insertionPoint, int relativeStartSlopeIndex, int relativePeakIndex) {
    peakShapes.get(segment).add(
      insertionPoint,
      peakToShapes(
        h.sharedZ(), h.sharedDef(), defScale, h.retractFrom(segment) + h.offset(segment), minZ,
        minDef, IntPair.of(relativeStartSlopeIndex, relativePeakIndex),
        this.segmentPeakColors.get(segment)));
  }

  public void removedPeakSlopes(int[] o) {
    int[] c = Arrays.copyOf(o, o.length);
    Arrays.sort(c);
    for (int i = c.length; --i >= 1;) {
      Preconditions.checkArgument(c[i] > c[i - 1]);
    }
    for (int i = c.length; --i >= 0;) {
      removedPeakSlope(c[i]);
    }
  }

  public void segment(int i) {
    this.segment = i;
  }
}
